/**
 * 
 */
package wordEvo.client;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Collections;
import java.util.List;
import java.util.Vector;

import javax.swing.JOptionPane;

import whipstaff.data.WhipPacket;
import whipstaff.tools.IOHelper;
import whipstaff.tools.OutputLog;
import whipstaff.tools.TimeHelper;
import whipstaff.wordevo.data.Request;
import whipstaff.wordevo.data.Result;
import whipstaff.wordevo.words.GoalWord;

/**
 * @author Jim Buck, Whipstaff Productions
 * @since August 10, 2011
 * @updated September 10, 2011
 */
public class EvoClient
{
	private OutputLog log;
	private int commPort = 6014;
	private String address = "localhost";
  
	private boolean available = true;
	private final int maxThreads = 32;
	
	private List<ClientThread> threadList;
	private List<Request> requestList;
	private List<Result> resultList;
	private String inputFile;
	
	private String alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
	
	private final long defaultChildren = 16, defaultInstances = 1;
	
	private long beginTime, endTime;
	
	public EvoClient(String file) {
		super();
		
		
		this.log = new OutputLog("./logs", "clientlog", true);
		this.inputFile = file;
		
		this.threadList = new Vector<ClientThread>();
		this.requestList = new Vector<Request>();
		this.resultList = new Vector<Result>();
	}
	
	public EvoClient() {
		this(null);
	}
	
	// Retrieves the outputlog
	public OutputLog getLog() {
		return this.log;
	}
	
	// Gets the port to use while sending Results to the server.
	public int getResultPort() {
		return this.commPort;
	}
	// Gets the address where requests are given and results are sent.
	public String getAddress() {
		return this.address;
	}
	// Gets and sets the flag indicating server availability.
	public void setConnectionStatus(boolean isAvailable) {
		this.available = isAvailable;
	}
	public boolean serverIsAvailable() {
		return this.available;
	}
	// ****************************************************************************************
	public void begin()
	{
		initiate();
		act();
		complete();
	}
	
	private void initiate() {
		this.inputFile = "";
		try {
			Socket socket = new Socket(this.address, this.commPort);
			this.sendSavedItems();
			socket.close();
		} catch (UnknownHostException e) {
			e.printStackTrace();
			this.log.printerr("UnknownHostException while checking for connection...");
		} catch (IOException e) {
			this.log.printerr("Connection Test Failed: Results will be saved to local filesystem.");
			this.available = false;
		}
		this.beginTime = System.nanoTime();
	}
	
	// Accept requests or run custom input file?
	private void act() {
		if(this.inputFile != "") {
			customInputFile();
		} else {
			if(promptUser())
				this.acceptRequests();
			else
				this.customInputFile();
		}
	}
	
	private void complete() {	
		do {
			cleanThreads();
		} while(this.threadList.size() > 0);
		this.endTime = System.nanoTime();
		
		this.log.println("Result List size: " + this.resultList.size());
		
		if(this.resultList.size() > 0) {
			saveToFile(this.resultList);
		}
		this.log.println("Client Complete! (" + TimeHelper.printTime(this.endTime - this.beginTime) + "s elapsed)");
		System.exit(0);
	}
	// ****************************************************************************************
	private void sendSavedItems() {
		List<File> listOfResultsList = IOHelper.getFileList("./results", ".result");
		
		if(listOfResultsList != null) {
			for(File file : listOfResultsList) {
				try {
					WhipPacket packet = new WhipPacket(file, "./wordevo/results", "", ".result");
					IOHelper.transmitObject(this.address, this.commPort, packet);
					file.delete();
				} catch (UnknownHostException e) {
					e.printStackTrace();
				} catch (IOException e) {
					e.printStackTrace();
					this.log.printerr("The server(" + this.address + ":" + this.commPort + ") " +
										"could not be reached. Program will retry next time.");
				}
			}
		}
	}
	
	private void saveToFile(List<Result> list) {
		
		
		try {
			for(Result result : list)
				IOHelper.saveToFile(result, new File("./results", IOHelper.getNewFileName("./results", "", ".result")));
		} catch (FileNotFoundException e) {
			this.log.printerr(e.getMessage());
			this.log.printerr("There was an error creating the file. Moving on...");
		} catch (IOException e) {
			this.log.printerr(e.getMessage());
			this.log.printerr("There was an error saving to file. Moving on...");
		}	
	}

	private boolean promptUser() {
		this.inputFile = JOptionPane.showInputDialog("Please enter a filename to parse: (Or leave blank to accept requests.)");
		
		if(this.inputFile == null)
			this.inputFile = "";
		
		return (this.inputFile == "");
	}
	
	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
	 * Steps if accepting requests:
	 * - Connect to WSP.
	 * - Check for available requests.
	 * - Ask user for number of packets/duration
	 * - Create and run new thread.
	 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 
	private void acceptRequests() {
		// TODO: Write request grabbing system.
		
		/* Steps:
		 * - Count available requests
		 * - Ask user how long/many?
		 * - Download
		 * - Clean and close. 
		 */
		
	}
	
	
	@SuppressWarnings("unchecked")
	private void customInputFile() {
		if(!this.inputFile.isEmpty()) {
			parseFile(this.inputFile);
			Collections.sort(this.threadList);
			beginThreads();
		} else {
			this.log.printerr("No file was specified, please restart the program.");
			System.exit(0);
		}
	}
	
	@SuppressWarnings("unchecked")
	private void parseFile(String file) {
		try {
			// Open the file that is the first
			// command line parameter
			FileInputStream reader = new FileInputStream(file);
			String chr;
			String str;

			// Read File Line By Line
			while (reader.available() > 0) {
				str = "";
				do {
					chr = "" + (char) reader.read();

					if (this.alpha.indexOf(chr.toUpperCase()) == -1) {
						break;
					} else
						str += chr;

				} while (reader.available() > 0);

				if (!str.isEmpty() && str.length() > 2) {
					boolean notFound = true;
					for(Request req : this.requestList) {
						if(req.getGoalWord().toString().equalsIgnoreCase(str)) {
							req.getGoalWord().setFreq(req.getGoalWord().getFreq() + 1);
							notFound = false;
							break;
						}
					}
					if(notFound)
						this.requestList.add(new Request(new GoalWord(str),  this.defaultInstances, this.defaultChildren));
					
				}

			}

			reader.close();
		} catch (Exception e) {
			this.log.printerr("Unable to open file. Please try again...");
			System.exit(1);
		}

		Collections.sort(this.requestList);
	} 
	
	private void beginThreads() {
		int size = this.requestList.size();
		for(int index = 0; index < size; index++) {
			ClientThread wordEvo = getThread();
			wordEvo.define(this.requestList.get(index));
			wordEvo.start();
			
			if(index % (int)((float)size / 3.0) == 0)
				System.gc();
		}
		
	}

	private ClientThread getThread() {				
		
		
		while(this.threadList.size() >= this.maxThreads) {
			cleanThreads();
		}
		ClientThread thread = new ClientThread(this);
		this.threadList.add(thread);
		return thread;
	}
	
	private void cleanThreads() {
		for(int i = 0; i < this.threadList.size(); i++) {
			if(!this.threadList.get(i).isAlive()) {
				this.threadList.remove(i);
			}
		}
	}
	
	public void saveForLater(Result r) {
		this.resultList.add(r);
	}
}
